﻿
<?php 




/*
типы лексем:
0 - неопределено
1 - словесная
2 - числовая
3 - знак  
    const UNDEFINED = 0;
    const VERBAL = 1;
    const NUMERIC = 2;
    const SIGN = 3;
    */ 
    
    /**
    * This function searches for order related errors in student's response by comparing it with correct answer.
    *
    * @param array $answer Array of correct answer's lexemes
    * @param array $response Array of student response's lexemes
    * @param array $errors Array of error messages, created in checking process
    */
    function autofeedback_order_check($answer, $response,&$errors)
    {
        //////echo '<br> order check';
        $lcs = array();
        $ans_lcs = array();
        autofeedback_string_lcs($answer,$response,$errors,$lcs,$ans_lcs);    // получение lcs в виде индексов (не ид-типов) правильного ответа
        //////echo ' lcs:<pre> ';
        //truent_r($lcs);
        //////echo '</pre>';
        
        $answerlength = count($answer);
        $responselength = count($response);
        $std_ns = array();    // массив соответствующих номеров 
        for($i=0;$i<count($response);$i++)
        {
            $std_ns[] = $response[$i]->corresponding;
        }
        $wrongs_corr = array();
        for($i=0;$i<$answerlength;$i++) {
            $lcs_contains = in_array($i,$lcs);
            if(!$lcs_contains) {    // лексема не входит в НБП - либо не там, либо нет совсем (неизвестно)
                    $wrongs_corr[] = $i;
            }
        }
        $ans_wrongs = array();
        for($i=0;$i<$responselength;$i++) {
            $lcs_contains = in_array($i,$ans_lcs);
            if(!$lcs_contains) {
                // лексема не входит в НБП - либо не там, либо лишняя (неизвестно)
                // проверка на предмет 'не там'
                $found = false;
                for ($j=0; $j<count($wrongs_corr) && !$found; $j++) {
                    if ($answer[$wrongs_corr[$j]]->number == $response[$i]->corresponding) {
                        // не там
                        if ($response[$i]->error!= error_message::NO_ERROR) {
                            $errors[] = new error_message($response[$i]->error,$answer[$wrongs_corr[$j]],$response[$i]);
                        }
                        $errors[] = new error_message(error_message::MISPLACED,$answer[$wrongs_corr[$j]],$response[$i]);
                        array_splice($wrongs_corr,$j);
                        $j--;
                        $found = true;    // выход из цикла
                    }
                }
                if(!$found) {
                    $ans_wrongs[] = $i;    // подходящего не найдено
                }
            }
        }
        if(count($wrongs_corr)!=0) {
            // есть что сравнивать на предмет 'указано неверно' (еще есть неправильные из правльного)
            for($i=0;$i<count($wrongs_corr);$i++) {
                if(array_search($wrongs_corr[$i]-1,$lcs) == array_search($wrongs_corr[$i]+1,$lcs)-1) {
                    //левый и правый идут в НОП подряд
                    //поиск соответствия в другом массиве
                    $found = false;
                    for($j=0;$j<count($ans_wrongs) && !$found;$j++) {
                        if(array_search($ans_wrongs[$j]-1,$ans_lcs) == array_search($ans_wrongs[$j]+1,$ans_lcs)-1) {
                            if( $answer[$wrongs_corr[$i]-1]->data == $response[$ans_wrongs[$j]-1]->data) {
                                if( $answer[$wrongs_corr[$i]+1]->data == $response[$ans_wrongs[$j]+1]->data) {
                                // тоже подряд, а это не вошло в НОП, строково левые/правые совпадают
                                $errors[] = new error_message(13,$answer[$wrongs_corr[$i]],$response[$ans_wrongs[$j]]);
                                array_splice($wrongs_corr,$i);
                                array_splice($ans_wrongs,$j);
                                $i--;
                                $j--;
                                }
                            }
                        }
                    }
                }
            }
        }
        // в массивах ошибочных лексем остались лексемы, для которых не обнаружено пары
        for($i=0;$i<count($wrongs_corr);$i++) {
            $errors[]= new error_message(error_message::MISSING, $answer[$wrongs_corr[$i]]);
        }
        for($i=0;$i<count($ans_wrongs);$i++) {
            $errors[] = new error_message(error_message::ODD, null, $response[$ans_wrongs[$i]]);
        }
        
    }
    /**
    * This function searches for longest subsequence common to correct answer and student's response.
    * 
    * @param array $answer Array of correct answer's lexemes
    * @param array $response Array of student response's lexemes
    * @param array $errors Array of error messages, created in checking process
    * @param array $answerlcs LCS represented in correct answer lexemes' indexes
    * @param array $responselcs LCS represented in student's answer lexemes' indexes
    */
    function autofeedback_string_lcs($answer, $response, 
                                     &$errors,&$answerlcs,&$responselcs) { 
        
        $m = count($answer);
        $n = count($response);

        $A = array();
        for($i=0; $i<$m+1; $i++) {
            $A[]= array_fill(0, $n+1, 0);
        }
        
        //////echo '</pre><br>computing lcs ';
      // compute length of LCS and all subproblems via dynamic programming
      //////echo '<br><pre>';
        for ($i = $m-1; $i >= 0; $i--) 
        {   //////echo "<br>i=$i<br>";
            for ($j = $n-1; $j >= 0; $j--) 
            {   
                //////echo " j=$j ";
                if ($answer[$i]->number == $response[$j]->corresponding)
                    //if(isset($A[$i+1][$j+1]))
                        $A[$i][$j] = $A[$i+1][$j+1] + 1;
                else 
                    //if(isset($A[$i+1][$j]) && isset($A[$i][$j+1]))
                    $A[$i][$j] = max($A[$i+1][$j], $A[$i][$j+1]);
                ////print_r($A);
            }
        }
        
        
        // composing and printing the LCS
        $i = 0; 
        $j = 0;
        $LCS = array();
        $LCS2 = array();
        while($i < $m && $j < $n) {
            if ($answer[$i]->number == $response[$j]->corresponding) {
            //////echo '<br>inner_lcs found match ';
                $LCS[] = $i;
                $LCS2[] = $j;
                if($response[$j]->error != error_message::NO_ERROR) {
                    // в студенческой половине соответствия опечатка      
                    //////echo ' lcs match ';
                    $errors[] = new error_message($response[$j]->error, $answer[$i]);
                }
                $i++;
                $j++;
            }
            elseif ($A[$i+1][$j] >= $A[$i][$j+1]) {
                $i++;
            } else {
                $j++;
            }
        }
        //////echo '</pre>';
        $answerlcs = $LCS;
        $responselcs = $LCS2;
        
        
    }

/**
 * Error message class.                                         
 *
 * Error message objects are created whenever an error is found in student's response.
 * The class contains definitions for error types and data necessery data to compose a feedback string.
 */
class error_message {
        /*
    типы ошибок:
    0 нет ошибки
    1 типичная ошибка
    2 опечатка 
    3 лишний пробел
    4 отсутствующий пробел
    5 лишняя лексема, образовавшаяся в результате лишнего пробела (должна игнорироваться)
    6 недостающая лексема, пропущенная в результате пропуска пробела
    7 погрешность в числовой лексеме
    8 сдвинутая точка в числовой
    9 неправильный разделитель целой и дробной части в дробном числе
    10 - не подходит под опечатку (лишняя)
    11 - в неправильном месте
    12 - недостающая
    13 - указано неправильно
    14 - отсутствующий пробел слева
    */
    const NO_ERROR = 0;                 // Everything is correct
    const TYPICAL = 1;                  // Typical error
    const TYPO = 2;                     // Typo
    const DISJUNCTION = 3;              // Odd separator in the middle of lexeme
    const CONJUNCTION = 4;              // Missing separator between lexemes
    const DISJUNCTION_RIGHT_PART = 5;   // Extra lexeme, appearing as a result of odd separator
    const CONJUNCTION_RIGHT_PART = 6;   // Missing lexeme, appearing as a result of missing space
    const TOLERANCE = 7;                // Infidelity in numeric value specification
    const POINT_MISPLACEMENT = 8;       // Misplaced decimal point in numeric value 
    const WRONG_DEC_POINT = 9;     // Wrong token used in decimal value 
    const ODD = 10;                     // Odd lexeme, not found in correct answer
    const MISPLACED = 11;               // Misplaced lexeme
    const MISSING = 12;                 // Lexeme not found in student's response
    const INCORRECT = 13;               // Lexeme specified incorrectly
    const CONJUNCTION_LEFT_PART = 14;   // This answer lexeme is missing from student's response as a result of missing space
    
    public $type;
    public $r_correct_answer_lexeme;
    public $r_answer_lexeme;
    public $message;
    private static $transcripts = array( 1 => 'Typical error in ',
                                 2 => ' содержит опечатку',
                                 3 => 'Лишний пробел в: ',
                                 4 => 'Пропущен пробел после: ',
                                 5 => '',
                                 6 => '',
                                 7 => 'Погрешность в: ',
                                 8 => 'Misplaced decimal point in ',
                                 9 => 'Wrong decimal point in ',
                                 10 => 'Лишнее: ',
                                 11 => ' стоит в неправильном месте',
                                 12 => ' отсутствует',
                                 13 => 'Указано неверно: ',
                                 14 => 'Пропущен пробел после: '); 
    /**
    * Constructor
    * 
    * @param int $type error type
    * @param generic_lexeme $answerlexeme Corresponding correct answer lexeme
    * @param generic_lexeme $responselexeme Corresponding student's answer lexeme
    */
    function __construct($type, $answerlexeme, &$responselexeme=NULL) {
        $this->type = $type;
        $this->r_correct_answer_lexeme = $answerlexeme;
        $this->r_answer_lexeme = $responselexeme;
        $this->message = self::$transcripts[$type];  
    }
    
    /**
    * This funciton composes feedback string based on error type and corresponding lexemes
    * @return string String error message
    */
    function GetMessage() {
        if ($this->type == error_message::TYPO ||
            $this->type == error_message::MISSING ||
            $this->type == error_message::MISPLACED)
        {
            return $this->r_correct_answer_lexeme->name.$this->message;
        }
        if($this->type != error_message::ODD && 
        $this->type != error_message::DISJUNCTION_RIGHT_PART && 
        $this->type != error_message::CONJUNCTION_RIGHT_PART && 
        $this->type != error_message::CONJUNCTION_LEFT_PART)
            return $this->message.' '.$this->r_correct_answer_lexeme->name;
        if($this->type == error_message::ODD)
            return $this->message.' '.$this->r_answer_lexeme->data;
        if($this->type == error_message::CONJUNCTION_LEFT_PART)
            return $this->message.' '.$this->r_correct_answer_lexeme->lexemes[$this->r_correct_answer_lexeme->number-1]->name;
        
        return '';
    }
}
/**
 * Correct spelling suggestion class                                         
 *
 * Suggestion objects are used in the process of finding corresponding lexemes in student's response
 */
class suggestion {
    public $errortype;
    public $correctdata;
    public $priority;
    public $additional_data; // дополнительные данные. Лишняя лексема, образовавшаяся в результате лишнего пробела или отсутствующая врезультате слияния
    public $responselexeme;
    
    /**
    * Constructor
    * 
    * @param int            $type error type
    * @param generic_lexeme $correct Corresponding correct answer lexeme
    * @param generic_lexeme $responselexeme Corresponding student's answer lexeme
    * @param int            $priority Suggestion's priority 
    */
    function __construct($errortype, $correct, $priority, $responselexeme, $add)
    {
        $this->errortype = $errortype;
        $this->correctdata = $correct;
        $this->priority = $priority;
        $this->responselexeme = $responselexeme;
        $this->additional_data = $add;
    }
}
/**
 * Parent lexeme class. All lexeme types should be derived from generic_lexeme class.                                         
 */
class generic_lexeme {
    public $number;    // порядковый номер лексемы
    public $corresponding;     // номер соответствующей лексемы в правильном ответе
    public $type;    // тип лексемы
    public $data;    // строковые данные лексемы
    public $iscorrect;    // лексема является написанной правильно
    public $correct_data;    // правильные строковые данные
    public $error;    // тип ошибки: 0 - правильно, 1 - типичная ошибка, 2 - опечатка, 3 - лишний пробел между словами, 4 - отсутствие пробела между словами.
    public $maxdistance;    // максимальная допустимая дистанция для принятия данного написания за опечатку
    public $maxdistancefactor;  // коэффициент максимального расстояния Л
    public $typicalerrors;    // массив вариантов типичных ошибок при написании данной лексемы
    public $next;
    public $lexemes;
    public $name;
    /**
    * Constructor
    * 
    * @param string         $data Lexeme data
    * @param int $number $answerlexeme Corresponding correct answer lexeme
    * @param array $array Corresponding student's answer lexeme
    */
    function __construct($data, $number, &$lexemes, $type, $name = NULL) {
        $this->maxdistancefactor = 4;
        $this->data=$data;
        $this->number=$number;
        $this->maxdistance = strlen($this->data)/$this->maxdistancefactor;
        $this->typicalerrors = array();
        $this->error=error_message::NO_ERROR;
        $this->iscorrect = false;
        $this->lexemes = &$lexemes;
        $this->type = $type;
        $suggestions = array();
        $this->name = $name;
    }
    
    /** Get next lexeme in array
    * @return generic_lexeme object - next lexeme in array, or NULL if this lexeme is last
    */
    function get_next() {
        if ($this->number == count($this->lexemes - 1) {
            return NULL;
        } else {
            return $this->lexemes[$this->number + 1];
        }
    }
    /** Suggestion making function
    * @param array $answer Correct answer lexemes array
    * @return array suggestions array
    */
    function get_suggestions(&$answer) {
        $suggestions = array();
        $answerlength = count($answer);
        // sweep through answer lexemes and suggest suggestions
        //////echo '<br>making suggestions. max distance = '.$this->maxdistance;
        for($i = 0; $i<$answerlength; $i++) {
            //////echo ' answer N '.$i;
            if ($answer[$i]->iscorrect == false) {
                //////echo '<br>spelling ';
                $distance = levenshtein($this->data, $answer[$i]->data);
                //////echo '<br>L = '.$distance;
                if ($distance == 0) {
                  //  ////echo ' typo check  found exact match ';
                    $suggestions[] = new suggestion (error_message::NO_ERROR, $this->data, 0, $i, -1);
                } else {    //////echo '<br>incorrect: '.$distance.' '.$this->data.'='.$answer[$i]->data;
                }
                
                if ($distance>0 && $distance<=$this->maxdistance && $this->iscorrect == false) {
                    //////echo ' typo ';
                    $suggestions[] = new suggestion (error_message::TYPO, $this->data, $distance*2, $i, -1);
                }
                // odd space check
                if ($i < $answerlength - 1 &&
                    $answer[$i+1]->iscorrect == false &&
                    $distance == strlen($answer[$i+1]->data) &&
                    $this->data == $answer[$i]->data.$answer[$i+1]->data && $this->iscorrect == false) {
                    //////echo ' space ';
                    $suggestions[] = new suggestion(error_message::DISJUNCTION, $this->data, 1, $i, $i+1);
                }
                
                // conjunction check
                if (isset($this->lexemes[$this->number + 1]) &&
                    $this->number < count($this->lexemes) &&
                    $distance == strlen($this->lexemes[$this->number + 1]->data) &&
                    $answer[$i]->data == $this->data.$this->lexemes[$this->number+1]->data && $this->iscorrect == false) {
                    $suggestions[] = new suggestion(error_message::CONJUNCTION, $this->data, 1, $i, $this->number+1);
                }
                
                
                if (isset($thsi->lexemes[$this->number - 1]) &&
                    $distance == strlen($this->lexemes[$this->number - 1]->data) &&
                    $answer[$i]->data == $this->lexemes[$this->number - 1]->data.$this->data && $this->iscorrect == false) {
                    $suggestions[] = new suggestion(error_message::CONJUNCTION_LEFT_PART, $this->data, 1, $i, $this->number-1);
                }
            }
        }
        return $suggestions
    }
    /** Get result of suggestion application without modifying initial lexeme arrays
    * @param array $answer Student's answer lexemes array
    * @param array $errors errors array
    * @param $suggestion suggestion to apply
    * @param array $resanswer resulting answer array
    * @param array $reserrors resulting errors array
    */
    function probe_suggestion ($answer, $errors, $suggestion, &$resanswer, &$reserrors) {
        // clone initial $answer
        foreach ($answer as $answerlexeme) {
            $resanswer[] = clone $answerlexeme;
        }
        // clone errors
        foreach ($errors as $error) {
            $reserrors[] = clone $error;
        }
        
        switch ($suggestion->errortype) {
            case error_message::NO_ERROR:   
                $resanswer[$suggestion->responselexeme]->iscorrect = true;
                $resanswer[$suggestion->responselexeme]->corresponding = $this->number;
                $resanswer[$suggestion->resopnselexeme]->error - error_message::NO_ERROR;
                $resanswer[$suggestion->responselexeme]->correct_data = $this->data;
                break;
            case error_message::TYPO:
                //echo '<br>typo';
                $resanswer[$suggestion->responselexeme]->iscorrect = true;
                $resanswer[$suggestion->responselexeme]->corresponding = $this->number;
                $resanswer[$suggestion->responselexeme]->error = error_message::TYPO;
                $resanswer[$suggestion->responselexeme]->correct_data = $this->data;
                //echo ' done';
                break;
            case error_message::DISJUNCTION:
                //echo '<br>disjunction';
                $resanswer[$suggestion->responselexeme]->iscorrect = true;
                $resanswer[$suggestion->responselexeme]->data = $this->data; // ?
                $resanswer[$suggestion->responselexeme]->error = error_message::DISJUNCTION;
                $resanswer[$suggestion->responselexeme]->correct_data = $this->data;
                $resanswer[$suggestion->additional_data]->error = error_message::DISJUNCTION_RIGHT_PART; // ?
                array_splice($resanswer, $suggestion->additional_data);
                break;
            case error_message::CONJUNCTION:
                //echo '<br>conjunction';
                $resanswer[$suggestion->responselexeme]->iscorrect = true;
                $resanswer[$suggestion->responselexeme]->corresponding = $this->number;
                $resanswer[$suggestion->responselexeme]->data = $this->data;
                $resanswer[$suggestion->responselexeme]->error = error_message::CONJUNCTION;
                $resanswer[$suggestion->responselexeme]->correct_data = $this->data;
                $newlexeme = new generic_lexeme($this->lexemes[$this->number + 1]->data, $suggestion->responselexeme + 1, $resanswer, 0);
                array_splice($resanswer, $suggestion->responselexeme + 1, count($resanswer), array_merge(array($newlexeme), array_slice($resanswer, $suggestion->responselexeme + 1))); // insert
                $resanswer[$suggestion->responselexeme + 1]->error = error_message::CONJUNCTION_RIGHT_PART;
                $resanswer[$suggestion->responselexeme + 1]->iscorrect = true;
                $resanswer[$suggestion->responselexeme + 1]->corresponding = $this->number + 1;
                $resanswer[$suggestion->responselexeme + 1]->correct_data = $this->lexemes[$this->number+1]->data;
                break;
            case error_message::CONJUNCTION_LEFT_PART:
                //echo '<br>C left';
                $resanswer[$suggestion->responselexeme]->iscorrect = true;
                $resanswer[$suggestion->responselexeme]->corresponding = $this->number;
                $resanswer[$suggestion->responselexeme]->data = $this->data;
                $resanswer[$suggestion->responselexeme]->error = error_message::CONJUNCTION_LEFT_PART;
                $resanswer[$suggestion->responselexeme]->correct_data = $this->data;
                $newlexeme = new generic_lexeme($this->lexemes[$this->number - 1]->data, $suggestion->responselexeme, $resanswer, 0);
                array_splice($resanswer, $suggestion->responselexeme, count($resanswer), array_merge(array($newlexeme), array_slice($resanswer, $suggestion->responselexeme))); // insert
                $resanswer[$suggestion->responselexeme]->error = error_message::CONJUNCTION_RIGHT_PART;
                $resanswer[$suggestion->responselexeme]->iscorrect = true;
                $resanswer[$suggestion->responselexeme]->corresponding = $this->number + 1;
                $resanswer[$suggestion->responselexeme]->correct_data = $this->lexemes[$this->number - 1]->data;
                for ($k = 0; $k < count($resanswer); $k++) {
                    $resanswer[$k]->number = $k;
                }
                break;
            }
    }
    /**
    * Typo detection function
    * @param array $answer student's answer lexemes array
    * @return suggestion Object representing chosen suggestion
    */
    function check_typo(&$answer) {
        //////echo '<br>check typos ';
        
        $suggetions = get_suggestions (&$answer);
        // pick one of suggestions
        $suggestionscount = count($suggestions);
        if($suggestionscount == 0) {
            $this->corresponding = 1;
            $this->iscorrect = false;
            $this->error = error_message::ODD;
            return NULL;
        } else {
            $chosen = 0;
            if ($suggestionscount == 1) {
                // Single suggestion
                $chosen = 0;
            } else {
                // Multiple suggestions
                foreach ($suggetsions as $suggestion) {
                    
                }
                $chosen = 0;
                $toppriority = $suggestions[0]->priority;
                // choose suggestion basesd on priority
                for ($i=0; $i<suggestionscount; $i++) {
                    if ($suggestions[$i]->priority < $toppriority) {
                        $chosen = $i;
                        $toppriority = $suggestions[$i]->priority;
                    }
                }
            }
            return $suggestions[$chosen];
        }
        
    }
    /**
    * Spelling check function
    * @param array $response Student's response lexemes array
    * @param array $errors Error messages array
    * @param array $chosensuggestions Array of suggestions chosen to be applied
    * @param array $coverage Array, representing which lexemes of student's response have been identified as correct or partially correct
    */
    function check_spelling(&$response, &$errors, &$chosensuggestions, &$coverage) {
       //echo '<br>Check_spelling '.$this->iscorrect;
        $found = false;
        for($i = 0; $i< count($response) && $found == false; $i++) {
            //echo ' <br>answer inside '.$response[$i]->data ;
            // exact match is now a high-priority suggestion
            
            /*
            if( $response[$i]->iscorrect == false){ 
                if($this->data == $response[$i]->data) {
                //echo ' found_exact ';
                $this->iscorrect = true;
                $this->corresponding = $i;
                $this->correctdata = $response[$i];
                $this->error = error_message::NO_ERROR;
                $response[$i]->iscorrect = true;
                $response[$i]->error = error_message::NO_ERROR;
                $response[$i]->corresponding = $this->number;
            } else{} }
            else {
                
            } */
            
        }
        
        $suggestions = array();
        $suggestions = $this->get_suggestions($response);
        $resresponsesets = array();   // array of modified sutdent's responses
        $reserrorsets = array();      // array of modified error arrays
        $coverages = array();         // array of coverages, provided by suggestions
        $suggestionsets = array();
        foreach ($suggestions as $suggestion) {
            // Probe suggestion
            $resresponse = array();
            $reserrors = array();
            // clone chosen suggestions
            $reschosensuggestions = array();
            foreach ($chosensuggestions as $chosensuggestion) {
                $reschosensuggestions[] = clone $chsoensuggestion;
            }
            
            probe_suggestion($response, $errors, $suggestion, $resresponse, $reserrors);
            $rescoverage = $coverage; // no need to clone array
            
            // if this is not last lexeme - make recursive call with probed arrays
            if ($this->number < count($this->lexemes)) {
                $this->get_next->check_spelling($resresponse, $reserrors, $reschosensuggestions, $rescoverage);
                // save results to arrays
                $coverages[] = $rescoverage;
                $resresponsesets[] = $resresponse;
                $reserrorsets[] = $reserrors;
                $suggestionsets[] = $reschosensuggestions;
            }
            
            
        }
        
        // roll tree back:
            // choose suggestions based on coverage and combined priority
            // 
        if($this->iscorrect != true) {
            $suggestion = $this->check_typo(&$response);
            if ($suggestion != NULL) {
                $this->apply_suggestion ($response, $errors, $suggestion);
                $errors[] = new error_message($suggestion->errortype, $this->number, $suggestion->responselexeme);
            } else {
                $this->iscorrect = false;
                $this->corresponding = -1;
                $this->error = error_message::MISSING;
                
            }
                               
                //////echo '</pre>';
                
                 ////print_r($errors); ////echo '<br>';
                 ////print_r($this);
                ////print_r($suggestion);
                //////echo '<pre>';
            /* $this->corresponding = 1;
            $this->iscorrect = false;
            $this->error = error_message::ODD; */
        }
    }
    
    
    
    /**
    * This function applies chosen suggestion and creating error message
    * @param array $answer correct answer lexemes array
    * @param array $errors Error messages array
    * @param suggestion $suggestion Object, defining chosen suggestion to apply
    */
    function apply_suggestion (&$answer, &$errors, $suggestion) {
        switch ($suggestion->errortype) {
            case error_message::TYPO:
                //////echo '<br>typo';
                $this->iscorrect = true;
                $this->corresponding = $suggestion->responselexeme;
                $answer[$suggestion->responselexeme]->iscorrect = true;
                $answer[$suggestion->responselexeme]->corresponding = $this->number;
                $answer[$suggestion->responselexeme]->error = error_message::TYPO;
                $answer[$suggestion->responselexeme]->correct_data = $this->data;
                //////echo ' done';
                break;
            case error_message::DISJUNCTION:
                //////echo '<br>disjunction';
                $this->iscorrect = true;
                $this->corresponding = $suggestion->responselexeme;
                $answer[$suggestion->responselexeme]->iscorrect = true;
                $answer[$suggestion->responselexeme]->data = $this->data; // ?
                $answer[$suggestion->responselexeme]->error = error_message::DISJUNCTION;
                $answer[$suggestion->responselexeme]->correct_data = $this->data;
                $answer[$suggestion->additional_data]->error = error_message::DISJUNCTION_RIGHT_PART; // ?
                array_splice($answer, $suggestion->additional_data);
                break;
            case error_message::CONJUNCTION:
                //////echo '<br>conjunction';
                $this->iscorrect = true;
                $this->corresponding = $suggestion->responselexeme;
                $answer[$suggestion->responselexeme]->iscorrect = true;
                $answer[$suggestion->responselexeme]->corresponding = $this->number;
                $answer[$suggestion->responselexeme]->data = $this->data;
                $answer[$suggestion->responselexeme]->error = error_message::CONJUNCTION;
                $answer[$suggestion->responselexeme]->correct_data = $this->data;
                $newlexeme = new generic_lexeme($this->lexemes[$this->number + 1]->data, $suggestion->responselexeme + 1, $answer, 0);
                array_splice($answer, $suggestion->responselexeme + 1, count($answer), array_merge(array($newlexeme), array_slice($answer, $suggestion->responselexeme + 1))); // insert
                $answer[$suggestion->responselexeme + 1]->error = error_message::CONJUNCTION_RIGHT_PART;
                $answer[$suggestion->responselexeme + 1]->iscorrect = true;
                $answer[$suggestion->responselexeme + 1]->corresponding = $this->number + 1;
                $answer[$suggestion->responselexeme + 1]->correct_data = $this->lexemes[$this->number+1]->data;
                break;
            case error_message::CONJUNCTION_LEFT_PART:
                //////echo '<br>C left';
                $this->iscorrect = true;
                $this->corresponding = $suggestion->responselexeme;
                $answer[$suggestion->responselexeme]->iscorrect = true;
                $answer[$suggestion->responselexeme]->corresponding = $this->number;
                $answer[$suggestion->responselexeme]->data = $this->data;
                $answer[$suggestion->responselexeme]->error = error_message::CONJUNCTION_LEFT_PART;
                $answer[$suggestion->responselexeme]->correct_data = $this->data;
                $newlexeme = new generic_lexeme($this->lexemes[$this->number - 1]->data, $suggestion->responselexeme, $answer, 0);
                array_splice($answer, $suggestion->responselexeme, count($answer), array_merge(array($newlexeme), array_slice($answer, $suggestion->responselexeme))); // insert
                $answer[$suggestion->responselexeme]->error = error_message::CONJUNCTION_RIGHT_PART;
                $answer[$suggestion->responselexeme]->iscorrect = true;
                $answer[$suggestion->responselexeme]->corresponding = $this->number + 1;
                $answer[$suggestion->responselexeme]->correct_data = $this->lexemes[$this->number - 1]->data;
                for ($k = 0; $k < count($answer); $k++) {
                    $answer[$k]->number = $k;
                }
                break;
            }
    }
}
/**
 * Numerical lexeme class. NOT CURRENTLY IN USE
 */
class num_lexeme extends generic_lexeme {
    private $maxpointshift;    // максимальный сдвиг точки в десятичной дроби
    private $maxtolerance;
    function __construct($data,$number,&$array,$type, $name = NULL) {
        $this->data=$data;
        $found = false;
        $this->number=count($array);
        $this->name = $name;
        $this->maxdistance = strlen($this->data)/4;
        $this->typicalerrors = array();
        $this->error=error_message::NO_ERROR;
        $this->iscorrect = false;
        $this->lexemes = &$array;
        $this->type = $type;
        $suggestions = array();
        $this->maxpointshift = 1;
        $this->maxtolerance = (0.0 + $this->data)/10;
    }
    function check_spelling(&$answer, &$errors) {
        ////echo ' spellling '.$this->data;
        $found = false;
        for($i = 0; $i< count($answer) && $found == false; $i++) {
            //////echo ' <br>answer inside '.$answer[$i]->data ;
            if( $answer[$i]->iscorrect == false){ 
                if($this->data == $answer[$i]->data) {
                //////echo ' found_exact ';
                $this->iscorrect = true;
                $this->corresponding = $i;
                $this->correctdata = $answer[$i];
                $this->error = error_message::NO_ERROR;
                $answer[$i]->iscorrect = true;
                $answer[$i]->error = error_message::NO_ERROR;
                $answer[$i]->corresponding = $this->number;
            }else{} }
            else {
                
            }
            
        }
        if($this->iscorrect == false) {
        //////echo  ' incorrect. suggestion: ';
                $suggestion = $this->check_typo(&$answer);
                ////print_r($suggestion);
                $this->apply_suggestion ($answer, $errors, $suggestion);
                //////echo ' num_error_applied ';
                $errors[] = new error_message($suggestion->type, $this->number, $suggestion->responselexeme);
                //////echo ' got suggestion: <pre>';
                ////print_r($suggestion);
                //////echo '<pre>';
            /* $this->corresponding = 1;
            $this->iscorrect = false;
            $this->error = error_message::ODD; */
        }
    }
    /**
    * Spelling check function
    * @param array $answer Correct answer lexemes array
    * @param array $errors Error messages array
    */
    function check_typo (&$answer) {
    //////echo '<br>num typo';
        $parentsuggestion = parent::check_typo(&$answer);
        
        $suggestions = array();
        $number_r_expr = '/[0-9]+(\.)?[0-9]*/';
        if ($parentsuggestion != NULL) {
            $suggestions[] = $parentsuggestion;
        }
        $answerlength = count($answer);
        for($i = 0; $i < $answerlength; $i++) {
            if ($answer[$i]->iscorrect == false &&
                preg_match ($number_r_expr, $answer[$i]->data) == 1) {
                $distance = levenshtein($this->data, $answer[$i]->data);
                // tolerance check
                $answernumber = 0.0 + $answer[$i]->data;
                $thisnumber = 0.0 + $this->data;
                if ( abs($thisnumber - $answernumber) < $this->maxtolerance) {
                    $suggestions[] = new suggestion(error_message::TOLERANCE, $this->data, 2, $i, -1);
                    //////echo ' tolerance ';
                }
                // decimal point shift check
                for ($s = 1; $s < $this->maxpointshift + 1; $s++) {
                    $shiftedanswer = $answernumber;
                    $shiftedthisnumber = $thisnumber;
                    for ( $coeff = 1; $coeff < $s + 1; $coeff++) {                                // встроенная функция степени десяти ? TODO
                        // check left shift:
                        $shiftedanswer *= 10;
                        if ($shiftedanswer == $this->data) {
                            $suggestions[] = new suggestion(error_message::POINT_MISPLACEMENT, $this->data, 2, $i, -1);
                            //////echo ' s_right ';
                        }
                        $shiftedthisnumber *= 10;
                        if ($shiftedthisnumber == $answer[$i]->data) {
                            //////echo ' s_left ';
                            $suggestions[] = new suggestion(error_message::POINT_MISPLACEMENT, $this->data, 2, $i, -1);
                        }
                    }
                }
                // wrong decimal point check
                if ( $i < count($answer) - 3) {
                    if ( $answer[$i+2]->iscorrect == false &&
                         $answer[$i+1]->data == "," &&
                         $answer[$i]->is_correct == false &&
                         fmod($answernumber, 1) == 0) {    // number in check is integer and it makes sense to check for point shift
                         ////echo ' point ';
                        $suggestions[] = new suggestion(error_message::WRONG_DEC_POINT, $this->data, 2, $i, $i+2);
                    }
                }
            }
        }
        // pick one of suggestions
        //////echo 'count: ';
        $suggestionscount = count($suggestions);
        //////echo $suggestionscount;
        if($suggestionscount == 0) {
            $this->corresponding = 1;
            $this->iscorrect = false;
            $this->error = error_message::ODD;
        } else {
            $chosen = 0;
            if ($suggestionscount == 1) {
                $chosen = 0;
            } else {
                $chosen = 0;
                //////echo '<pre>';
                ////print_r($suggestions);
                //////echo '</pre>';
                $toppriority = $suggestions[0]->priority;
                // choose suggestion basesd on priority
                for ($i=0; $i<$suggestionscount; $i++) {
                    if ($suggestions[$i]->priority < $toppriority) {
                        $chosen = $i;
                        $toppriority = $suggestions[$i]->priority;
                    }
                }
            }
        }
        return $suggestions[$chosen];
    }
    /**
    * This function applies chosen suggestion and creating error message
    * @param array $answer Student's answer lexemes array
    * @param array $errors Error messages array
    * @param suggestion $suggestion Object, defining chosen suggestion to apply
    */
    function apply_suggestion (&$answer, &$errors, $suggestion) {
        parent::apply_suggestion(&$answer, &$errors, $suggestion);
        if ($this->iscorrect != true) {
            switch ($suggestion->errortype) {
                case error_message::POINT_MISPLACEMENT:
                    $this->iscorrect = true;
                    $this->corresponding = $suggestion->responselexeme;
                    $answer[$suggestion->responselexeme]->iscorrect = true;
                    $answer[$suggestion->responselexeme]->corresponding = $this->number;
                    $answer[$suggestion->responselexeme]->error = error_message::POINT_MISPLACEMENT;
                    $answer[$suggestion->responselexeme]->correct_data = $this->data;
                    break;
                case error_message::WRONG_DEC_POINT:
                    $this->incorrect = true;
                    $this->corresponding = $suggestion->responselexeme;
                    $answer[$suggestion->responselexeme]->iscorrect = true;
                    $answer[$suggestion->responselexeme]->data = $this->data;
                    $answer[$suggestion->responselexeme]->error = error_message::WRONG_DEC_POINT;
                    $answer[$suggestion->responselexeme]->correct_data = $this->data;
                    $answer[$suggestion->additional_data]->error = error_message::DISJUNCTION_RIGHT_PART;
                    $answer[$suggestion->responselexeme + 1]->error = error_message::DISJUNCTION_RIGHT_PART;
                    array_splice($answer, $suggestion->responselexeme + 1, 2);
                    break;
            }
        }
    }
    
    
}
/**
* String lexemee class NOT CURRENTLY IN USE
*/
class str_lexeme extends generic_lexeme {
    /** Constructor.
    * @param string $newData - строковые данные лексемы
    * @param int $newNumber - номер лексемы в массиве лексем
    */ 
    function __construct($data,$number,&$array,$type,$name) {
        $this->name=$name;
        $this->data=$data;
        $this->number=count($array);
        $this->maxdistance = strlen($this->data)/4;
        $this->typicalerrors = array();
        $this->error=error_message::NO_ERROR;
        $this->iscorrect = false;
        $this->lexemes = &$array;
        $this->type = $type;
        $suggestions = array();
    }
    
    function spelling (&$answer, &$errors) {
        
    }
    
    
}

/**
 * Processing function
 * @param $input_line Student's response
 * @param $answers Array of correct answers
 * @param $feedback Compsed feedback string
 * @param $errors Array of detected errors
 */
function autofeedback_process($input_line, $answers, &$feedback, &$errors) {
    global $DB;
    $responselexemes = array();
    $input = array();
    $name_r_expr = '/[a-zA-Z_][a-zA-Z0-9]*|_[a-zA-Z_][a-zA-Z_0-9+]/';    // слово, нач с буквы
    $number_r_expr = '/\b[0-9]+(\.)?[0-9]*\b/';
    $type_r_expr = '/void|int/';
    $leftParenthes_r_expr = '/\(/';
    $rightParenthas_r_expr = '/\)/';
    $comma_r_expr = '/,/';
    $semicolon_r_expr = '/;/';
    $item_r_e = '/([^ \t\(\),;]+)|([\(\),;:])/';
    $type_name_r_e = '/(struct[ ]+)?(([a-zA-Z_][a-zA-Z0-9]*|_[a-zA-Z_][a-zA-Z_0-9]+)[ ]+([a-zA-Z_][a-zA-Z0-9]*|_[a-zA-Z_][a-zA-Z_0-9]+))/';
    
    $errorsets = array();   // contains error arrays created for each answer
    $responsesets = array();    // response lexemes arrays altered in the process
    $counter = 0;
    // reponse lexemes are constant for all answers and are extracted outisde the loop.
    $item_number = preg_match_all($item_r_e, $input_line, $items);
    $i=0;
    foreach($items[0] as $item) {
        $responselexemes[] = new generic_lexeme($item, $i, $responselexemes, 0);
        $i++;
    }
    // check all answers and pick the closest 
    //////echo '<br>check answers<br>';
    ////echo '<br>initial: <br><pre>';
    ////print_r($responselexemes);
    foreach($answers as $answer) {
        //////echo ' answer being checked: '.$answer->answer;
        
        // clone initial responselexemes so it will not be affected in process
        $newresopnseset = array();
        foreach ($responselexemes as $responselexeme) {
            $newresponseset[] = clone $responselexeme;
        }
        $responsesets[] = $newresponseset;
        $answerlexemes = array();
        // get lexemes from db
        $lexemerecords = $DB->get_records_select ('autofeedback_lexemes', 'answerid = '.$answer->id);
        foreach ($lexemerecords as $lexemerecord) {
            $answerlexemes[] = new generic_lexeme($lexemerecord->text, $lexemerecord->serialnumber, $answerlexemes, 1, $lexemerecord->name); 
        }
        
       
        /*
        if (preg_match ($number_r_expr, $answer->answer) == 1) {
            $answerlexemes[] = new num_lexeme($answer->answer, $counter, $answerlexemes, 1);
        } else {
            $answerlexemes[] = new generic_lexeme($answer->answer, $counter, $answerlexemes, 1, $answer->feedback);
        }
        $counter++;*/
        
        // spelling check:
        // launch recursion
        $answerlexemes[0]->check_spelling($responsesets[count($responsesets)-1], $errors);
        
        /* old non-recursive call:
        for($i=0; $i<count($answerlexemes); $i++) {
            $answerlexemes[$i]->check_spelling($responsesets[count($responsesets)-1], $errors);
            $errors = array();
        
            for($c=0; $c<count($answerlexemes);$c++) {
                if( isset ($responsesets[count($responsesets)-1][$c]) &&
                    $responsesets[count($responsesets)-1][$c]->iscorrect == false) {
                    //////echo '<br> uncorrect on 2nd run ';
                    $responsesets[count($responsesets)-1][$c]->corresponding = 150;
                    $responsesets[count($responsesets)-1][$c]->error = error_message::ODD;
                    }
            }
            */
            
        /* $j=0;
        while($correct_lexemes[$i]->iscorrect == false &&
                $j<count($answerlexemes) ) {    
            if($correct_lexemes[$i]->data == $answerlexemes[$j]->data && $answerlexemes[$j]->iscorrect == false) {
                $correct_lexemes[$i]->iscorrect = true;
                $correct_lexemes[$i]->corresponding = $j;
                $answerlexemes[$j]->iscorrect = true;
                $answerlexemes[$j]->error = error_message::NO_ERROR;
                $answerlexemes[$j]->corresponding = $correct_lexemes[$i]->number;
            }
            $j++;
        } */
        }
        // order check
        autofeedback_order_check($answerlexemes, $responsesets[count($responsesets)-1], $errors);
        ////echo '<br>check complete. Errors:<pre>';
        foreach ($errors as $error) {
            ////echo $error->type.' ';
        }
       // //print_r($errors);
        $errorsets[] = $errors;
        //echo '<br>End: ';
        ////print_r($responselexemes);
        ////echo '</pre>';
    }
    //$typename_number = preg_match_all($type_name_r_e,$input_line,$type_names);
    //$c = 1;
    /* for( $c =0; $c< count($correct_lexemes);$c++) {
        if($correct_lexemes[$c]->iscorrect == false) {
            $correct_lexemes[$c]->spelling($answerlexemes,$errors);
        }
    }     */
    ////echo '<br>errorsets <pre>';
    ////print_r($errorsets);
    // pick most apropriate errorset 
    $leasterrors = $errorsets[0];
    $chosenset = 0;
    for ($i=0; $i<count($errorsets); $i++) {
        ////echo ' count='.count($errorsets[$i]);
        if (count($errorsets[$i]) < $leasterrors) {
            ////echo ' passed ';
            $leasterrors = count($errorsets[$i]);
            $chosenset = $i;
        } else { ////echo ' not_passed ';
        }
    }
    
    //////echo '<br>processed<pre>';
    ////print_r($errorsets);
    //////echo '</pre>';
    $feedback_string = '';
    for($i=0;$i<count($errorsets[$chosenset]);$i++) {
        $feedback_string = $feedback_string.'<br>'.$errorsets[$chosenset][$i]->GetMessage();
    }
    $feedback = $feedback_string;
    return count($errors);
}